package org.wzy.spring.framework.context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wzy.spring.framework.annotation.Autowired;
import org.wzy.spring.framework.annotation.Controller;
import org.wzy.spring.framework.annotation.Service;
import org.wzy.spring.framework.beans.BeanWrapper;
import org.wzy.spring.framework.beans.BeanWrapperImpl;
import org.wzy.spring.framework.beans.factory.BeanDefinitionReader;
import org.wzy.spring.framework.beans.factory.config.BeanDefinition;
import org.wzy.spring.framework.beans.factory.config.BeanPostProcessor;
import org.wzy.spring.framework.core.BeanFactory;
import org.wzy.spring.framework.transaction.config.AopAutoProxyConfigurer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 模拟 Spring AbstractApplicationContext 实现
 *
 * @author 王政豫
 * @date 2018/5/22 15:09
 */
public abstract class AbstractApplicationContext implements BeanFactory, ApplicationContext {

    /**
     * 保存已注册的所有 BeanDefinition, 实际上就是所谓的 "IOC 容器"
     */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(10);
    /**
     * 用来保存注册式单例的容器
     */
    private final Map<String, Object> singletonBeanCacheMap = new ConcurrentHashMap<>(10);
    /**
     * 用来存储所有的被代理后的对象
     */
    private final Map<String, BeanWrapper> beanWrapperMap = new ConcurrentHashMap<>(10);
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private String[] configLocations;
    private BeanDefinitionReader beanDefinitionReader;
    private String id = this.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(this));

    /**
     * 构造方法定义 configLocations, 这里会直接执行定位、加载和初始化过程
     *
     * @author wzy_h
     * @date 2018/5/19 22:50
     * @since 1.8
     */
    protected AbstractApplicationContext(String... configLocations) {
        this.setConfigLocations(configLocations[0]);
    }

    /**
     * 模拟 Spring IOC AbstractApplicationContext 中的 refresh() 方法
     *
     * @author wzy_h
     * @date 2018/5/19 22:43
     * @since 1.8
     */
    void refresh() throws Exception {
        /* 1.定位 */
        /* 2.加载 */
        beanDefinitionReader = new BeanDefinitionReader(configLocations);

        /* 3.注册, 到这一步为止容器初始化完毕 */
        doRegisterBeanDefinition(beanDefinitionReader.getBeanDefinitions());

        /* 4.依赖注入(lazy-init=false时需要执行, 即自动调用 getBean() 方法) */
        doAutowired();

        /* 5.调用由子类实现的 onRefresh 实现, 如初始化 SpringMVC */
        onRefresh();
    }

    /**
     * 执行自动化的依赖注入
     *
     * @author wzy_h
     * @date 2018/5/20 19:32
     * @since 1.8
     */
    private void doAutowired() throws Exception {
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                Object bean = getBean(beanName);
                logger.info("getBean({}) retured -> {}", beanName, bean);
            }
        }
    }

    /**
     * 向 Spring IOC 容器中注册 BeanDefinition
     *
     * @param beanDefinitionList beanDefinitionList
     * @author wzy_h
     * @date 2018/5/19 23:55
     * @since 1.8
     */
    private void doRegisterBeanDefinition(List<String> beanDefinitionList) {
        if (null == beanDefinitionList || beanDefinitionList.isEmpty()) {
            logger.error("beanDefinitionList 为空, 无法继续执行注册操作!");
            throw new NullPointerException("beanDefinitionList 为空, 无法继续执行注册操作!");
        }

        for (String beanDefinitionName : beanDefinitionList) {
            Class<?> clazz = null;
            try {
                clazz = Class.forName(beanDefinitionName);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            // 默认是类名首字母小写, 或是自定义名称, 或是接口注入
            // 接口无法实例化, 需要使用其实现类来实例化
            if (null != clazz && clazz.isInterface()) {
                continue;
            }

            // 将 BeanDefinition 注册到 IOC 容器中
            BeanDefinition beanDefinition = beanDefinitionReader.registerBeanDefinition(beanDefinitionName);
            beanDefinitionMap.putIfAbsent(beanDefinition.getFactoryBeanName(), beanDefinition);

            // 如果该 Bean 实现了接口, 则保留该接口以便后面属性的自动注入(简化版, 实际上未必实现的接口中的方法会被调用)
            Class<?>[] clazzInterfaces;
            if (clazz != null) {
                clazzInterfaces = clazz.getInterfaces();
                for (Class<?> clz : clazzInterfaces) {
                    // 多个实现类时直接覆盖, Spring 原生处理会报错(按类型注入时)
                    beanDefinitionMap.put(clz.getName(), beanDefinition);
                }
            }
        }
    }

    @Override
    public Object getBean(String beanName) throws Exception {
        // 获取到 BeanDefinition 对象, 并获取 className
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        Object instance = initializeBean(beanDefinition);
        if (null == instance) {
            return null;
        }

        // 生成通知事件
        BeanPostProcessor beanPostProcessor = new BeanPostProcessor();
        // 在实例初始化之前调用一次通知事件
        instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);

        BeanWrapper beanWrapper = new BeanWrapperImpl(instance);
        ((BeanWrapperImpl) beanWrapper).setBeanPostProcessor(beanPostProcessor);
        ((BeanWrapperImpl) beanWrapper).setAopAutoProxyConfigurer(initializeAopConfigurer(beanDefinition));
        beanWrapperMap.put(beanName, beanWrapper);

        // 在实例初始化之后调用一次通知事件
        instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);

        // 依赖注入
        populateBean(beanName, instance);

        // 相当于给自己留有了可操作的空间
        return beanWrapperMap.get(beanName).getWrappedInstance();
    }

    /**
     * 对 Bean 执行依赖注入
     *
     * @author wzy_h
     * @date 2018/5/20 19:41
     * @since 1.8
     */
    private void populateBean(String beanName, Object instance) {
        Class<?> instanceClass = instance.getClass();

        // 只有符合要求的 Bean 才会被注入
        if (!instanceClass.isAnnotationPresent(Controller.class) && !instanceClass.isAnnotationPresent(Service.class)) {
            logger.warn("beanName={}, instance={}, 不符合依赖注入要求, 自动跳过...", beanName, instance);
            return;
        }

        logger.info("{} 准备开始依赖注入...", beanName, instance);

        Field[] declaredFields = instanceClass.getDeclaredFields();
        for (Field field : declaredFields) {
            // 加了 @Autowired 注解的成员变量才会被注入
            if (field.isAnnotationPresent(Autowired.class)) {
                Autowired autowired = field.getAnnotation(Autowired.class);
                String autowiredBeanName = autowired.value().trim();

                // TODO 这里需要特殊处理一下, 假设A/B两个类都实现了ServiceA接口, 若不指定别名则需要报错提醒用户

                // 如果找到别名, 就用别名注入, 否则直接使用 beanName 注入
                if ("".equals(autowiredBeanName)) {
                    // 这里写死为 Service 接口的包名+接口名称
                    autowiredBeanName = field.getType().getName();
                }
                field.setAccessible(true);
                try {
                    field.set(instance, beanWrapperMap.get(autowiredBeanName).getWrappedInstance());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 根据 BeanDefinition 对象获取一个 Bean 实例并返回
     *
     * @param beanDefinition beanDefinition 实例
     * @author wzy_h
     * @date 2018/5/20 18:56
     * @since 1.8
     */
    private synchronized Object initializeBean(BeanDefinition beanDefinition) {
        // 通过 className 来反射
        Object instance = null;
        String beanClassName = beanDefinition.getBeanClassName();
        try {
            if (singletonBeanCacheMap.containsKey(beanClassName)) {
                instance = singletonBeanCacheMap.get(beanClassName);
            } else {
                Class<?> clazz = Class.forName(beanClassName);
                instance = clazz.newInstance();
                singletonBeanCacheMap.put(beanClassName, instance);
            }

        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
        return instance;
    }

    private AopAutoProxyConfigurer initializeAopConfigurer(BeanDefinition beanDefinition)
            throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException {
        AopAutoProxyConfigurer aopAutoProxyConfigurer = new AopAutoProxyConfigurer();

        String pointCutExpression = this.beanDefinitionReader.getConfigurations().getProperty("pointCut");
        Pattern pattern = Pattern.compile(pointCutExpression);

        String[] before = this.beanDefinitionReader.getConfigurations().getProperty("aspectBefore").split("\\s");
        String[] after = this.beanDefinitionReader.getConfigurations().getProperty("aspectAfter").split("\\s");

        // 需要拿到 beanDefinition 中的类所有方法与 application.properties 中定义的匹配规则相匹配
        String beanClassName = beanDefinition.getBeanClassName();
        Class<?> beanDefinitionClass = Class.forName(beanClassName);

        // 获取到自定义切面类, 如 LogAspect
        Class<?> aspectClass = Class.forName(before[0]);
        // 循环获取 LogAspect 下的所有 public 方法
        for (Method method : beanDefinitionClass.getMethods()) {
            // public .* org\.wzy\.spring\.framework\.webmvc\.demo\.service\..*Service.*\(.*\)
            // public java.lang.String org.wzy.spring.framework.webmvc.demo.service.DemoService.add(java.lang.String)
            Matcher matcher = pattern.matcher(method.toString());
            if (matcher.matches()) {
                logger.info("initializeAopConfigurer method={}, beanClassName={} matched successfully!", method.toString(), beanDefinitionClass.getName());
                // 说明当前类中的当前方法符合 Aop 匹配要求, 将其加入 config 中
                aopAutoProxyConfigurer.put(method, aspectClass.newInstance(),
                        new Method[]{aspectClass.getMethod(before[1]), aspectClass.getMethod(after[1])});
            }
        }

        return aopAutoProxyConfigurer;
    }

    private void setConfigLocations(String... configLocations) {
        if (configLocations != null) {
            Objects.requireNonNull(configLocations, "Config locations must not be null");
            this.configLocations = new String[configLocations.length];
            for (int i = 0; i < configLocations.length; i++) {
                this.configLocations[i] = configLocations[i].trim();
            }
        } else {
            this.configLocations = null;
        }
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public String getApplicationName() {
        return "";
    }

    public String[] getBeanDefinitionNames() {
        String[] emptyStringArray = new String[this.beanDefinitionMap.size()];
        return this.beanDefinitionMap.keySet().toArray(emptyStringArray);
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public Properties getConfigurations() {
        return this.beanDefinitionReader.getConfigurations();
    }

    protected void onRefresh() {
        // For Subclasses, do nothing by default.
    }

    protected abstract void refreshBeanFactory();
}
